import time
from werkzeug.datastructures.structures import MultiDict

from webapp.models import Album, Tag, Pic, Volume, Actress, pic_albums, pic_tags, pic_volumes, pic_actresses, Setting
from webapp.const import RedisKey
from datetime import datetime
from flask import url_for
from webapp import db
from sqlalchemy.sql import or_, func, desc
from .logs import logger
from . import cache_utils as cache
import traceback
import json


def now():
    return str(datetime.now())[:19]


def error_catching(function):
    def wrapper(*args, **kwargs):
        try:
            return function(*args, **kwargs)
        except:
            msg = traceback.format_exc()
            logger.error('[{0}()] [args: {1}], [kwargs: {2}]: {3}'.format(function.__name__, args, kwargs, msg))
            db.session.close()
    return wrapper


class CheckInfo:
    @staticmethod
    def check_task_pics_count():
        """图片文件数量"""
        return cache.get('check_task_pics_count', content_type='int')

    @staticmethod
    def check_task_thumbnail_count():
        """缩略图数量"""
        return cache.get('check_task_thumbnail_count', content_type='int')

    @staticmethod
    def check_task_checktime():
        """检查时间"""
        return cache.get('check_task_checktime', content_type='str')

    @staticmethod
    def check_task_dbr_lost_count():
        return cache.get('check_task_dbr_lost_count', content_type='int')

    @staticmethod
    def check_task_file_lost_db_count():
        return cache.get('check_task_file_lost_db_count', content_type='int')

    @staticmethod
    def check_task_thumbnail_lost_count():
        return cache.get('check_task_thumbnail_lost_count', content_type='int')

    @staticmethod
    def check_task_file_lost_thumbnail_count():
        return cache.get('check_task_file_lost_thumbnail_count', content_type='int')

    @staticmethod
    def check_tasks_unfiled_pic_count():
        """未加入任何相册和标签的图片数量"""
        return cache.get('check_tasks_unfiled_pic_count', content_type='int')


def search_(key):
    keyword_pic = []
    keyword_album = []
    keyword_tag = []
    keyword_volume = []
    key_list = key.lower().split(' ')
    for key in key_list:
        if key.find(':') != -1:
            label, text = key.split(':', maxsplit=1)
            text_like = ''.join(['%', text, '%'])

            if label == 'star':
                keyword_pic.append(Pic.rate == text)
                keyword_album.append(Album.rate == text)
            elif label == 'tag':
                keyword_tag.append(or_(Tag.name.like(text_like), Tag.id.like(text_like)))
            elif label == 'album':
                keyword_album.append(or_(Album.name.like(text_like), Album.id.like(text_like)))
            elif label == 'volume':
                keyword_volume.append(or_(Volume.name.like(text_like), Volume.id.like(text_like)))
            elif label == 'pic':
                keyword_pic.append(or_(Pic.note.like(text_like), Pic.file_name.like(text_like), Pic.id.like(text_like)))
            elif label == 'size':
                if text == 's':
                    keyword_pic.append(Pic.resolution < 1000000)
                elif text == 'm':
                    keyword_pic.extend([1000000 <= Pic.resolution, Pic.resolution < 2000000])
                elif text == 'l':
                    keyword_pic.append(2000000 <= Pic.resolution)
            else:
                pass
        else:
            text_like = ''.join(['%', key, '%'])
            keyword_tag.append(or_(Tag.name.like(text_like), Tag.id.like(text_like)))
            keyword_album.append(or_(Album.name.like(text_like), Album.id.like(text_like)))
            keyword_volume.append(or_(Volume.name.like(text_like), Volume.id.like(text_like)))
            keyword_pic.append(or_(Pic.note.like(text_like), Pic.file_hash.like(text_like), Pic.id.like(text_like)))

    result = []
    if keyword_album:
        result.extend(Album.query.filter(*keyword_album).all())
    if keyword_tag:
        result.extend(Tag.query.filter(*keyword_tag).all())
    if keyword_volume:
        result.extend(Volume.query.filter(*keyword_volume).all())
    if keyword_pic:
        result.extend(Pic.query.filter(*keyword_pic).all())

    response = []
    for resp in result:
        try:
            label = resp.label
            end_point = 'pic.one_{}'.format(label)

            result_dict = {
                'id': resp.id,
                'type': label,
                'text': resp.note_html() if label == 'pic' else resp.name_html(),
                'rate': resp.rate_html() if label != 'tag' else '',
                'pv': resp.pv_html(),
                'url': url_for(end_point, uid=resp.id, source='search'),
                'img': resp.thumbnail_img if label == 'pic' else resp.cover,
                'volumes': resp.volumes_html() if label == 'pic' else '',
                'albums': resp.albums_html() if label == 'pic' else '',
                'tags': resp.tags_html() if label == 'pic' else '',
                'amount': resp.amount_html() if label != 'pic' else '',
                'create_time': resp.create_time_html()
            }
            response.append(result_dict)
        except Exception as e:
            pass
    return response


def get_pic_context(args: MultiDict):
    """

    :param pic_id:
    :param source_type:
    :param source_id:
    :return:
    """
    pic_id = args.get('pic_id', type=int)
    source_type = args.get('source')
    source_id = args.get('source_id')
    next_pic_id = None
    prev_pic_id = None
    if source_type == 'home':
        next_pic = Pic.query.filter(Pic.id < pic_id).order_by(desc('id')).first()
        if next_pic:
            next_pic_id = next_pic.id
        prev_pic = Pic.query.filter(Pic.id > pic_id).order_by('id').first()
        if prev_pic:
            prev_pic_id = prev_pic.id

    elif source_type in ('album', 'volume', 'tag', 'actress'):
        # todo 需要根据相册排序
        if source_type == 'album':
            album = Album.query.filter_by(id=source_id).first()
            pic_list = album.pics_list
        elif source_type == 'volume':
            volume = Volume.query.filter_by(id=source_id).first()
            pic_list = volume.pics_list
        elif source_type == 'actress':
            actress = Actress.query.filter_by(id=source_id).first()
            pic_list = actress.pics_list
        else:
            tag = Tag.query.filter_by(id=source_id).first()
            pic_list = tag.pics_list

        index = pic_list.index(pic_id)
        if index > 0:
            prev_pic_id = pic_list[index - 1]
        try:
            next_pic_id = pic_list[index + 1]
        except IndexError:
            pass

    elif source_type == 'explore':
        unfiled = args.get('unfiled', type=bool, default=False)
        if unfiled:
            next_pic = Pic.query.filter(Pic.albums == None, Pic.actresses == None).order_by(func.random()).first()
        else:
            next_pic = Pic.query.order_by(func.random()).first()
        next_pic_id = next_pic.id


    return {
        "prev_url": url_for('pic.one_pic', uid=prev_pic_id, **args.to_dict()) if prev_pic_id else "",
        "next_url": url_for('pic.one_pic', uid=next_pic_id, **args.to_dict()) if next_pic_id else "",
    }


def pic_source(source_type, source_id=0):
    """
    计算图片的source页面
    :param source_type:
    :param source_id:
    :return:
    """
    source = dict()
    if source_type in ('home', 'explore', 'search', 'archive'):
        source['url'] = url_for('pic.{}'.format(source_type))
        source['name'] = source_type.capitalize()
    if source_type == 'recycle':
        source['url'] = url_for('work.recycle')
        source['name'] = source_type.capitalize()
    elif source_type == 'album':
        album = Album.query.filter_by(id=source_id).first()
        source['name'] = album.name
        source['url'] = url_for('pic.one_album', uid=source_id)
    elif source_type == 'tag':
        tag = Tag.query.filter_by(id=source_id).first()
        source['name'] = tag.name
        source['url'] = url_for('pic.one_tag', uid=source_id)
    elif source_type == 'volume':
        volume = Volume.query.filter_by(id=source_id).first()
        source['name'] = volume.name
        source['url'] = url_for('pic.one_volume', uid=source_id)
        source['actress_id'] = volume.actress_id
        source['actress_name'] = volume.actress.name
    elif source_type == 'actress':
        actress = Actress.query.filter_by(id=source_id).first()
        source['name'] = actress.name
        source['url'] = url_for('pic.one_actress', uid=source_id)
    return source


def pic_page(pic_id, source_type, source_id=0):
    """
    计算图片在当前栏目下所在页面
    :param pic_id:
    :param source_type:
    :param source_id:
    :return:
    """
    if source_type == 'home':
        index = Pic.query.filter(Pic.id > pic_id).count()
    elif source_type == 'album':
        pic_abstract = db.session.query(pic_albums).filter_by(album_id=source_id, pic_id=pic_id).first()
        index = db.session.query(pic_albums).filter(pic_albums.c.album_id == source_id, pic_albums.c.time > pic_abstract.time).count()
    elif source_type == 'tag':
        pic_abstract = db.session.query(pic_tags).filter_by(tag_id=source_id, pic_id=pic_id).first()
        index = db.session.query(pic_tags).filter(pic_tags.c.tag_id == source_id, pic_tags.c.time > pic_abstract.time).count()
    else:
        index = 0

    # page = index // Setting.get('per_page_pics') + 1
    # 原来需要在最后+1，改成第一页ajax加载后不需要了
    page = index // Setting.get('per_page_pics')

    return page


def archive_albums_info(label):
    source_data = []

    if label == 'album':
        album_cache = AlbumCache()
        source = list(album_cache.all().values())
        source.sort(key=lambda x: x.get('pv'), reverse=True)
        endpoint = 'pic.one_album'
    elif label == 'tag':
        tag_cache = TagCache()
        source = list(tag_cache.all().values())
        source.sort(key=lambda x: x.get('pv'), reverse=True)
        endpoint = 'pic.one_tag'
    elif label == 'volume':
        volume_cache = VolumeCache()
        source = list(volume_cache.all().values())
        source.sort(key=lambda x: x.get('pv'), reverse=True)
        endpoint = 'pic.one_volume'
    elif label == 'actress':
        actress_cache = ActressCache()
        source = list(actress_cache.all().values())
        source.sort(key=lambda x: x.get('pv'), reverse=True)
        endpoint = 'pic.one_actress'
    else:
        source = []
        endpoint = ''

    if source:
        for obj in source:
            if obj.get('name', '').startswith('备用'):
                continue
            resp = '''<span class="archive-link {css_size}"><a href="{url}" title="PV:{pv}">{name}</a>
            ({total})</span>'''.format(
                css_size=title_size(obj.get('count')),
                url=url_for(endpoint, uid=obj.get('id')),
                pv=obj.get('pv'),
                name=obj.get('name'),
                total=obj.get('count')
            )
            source_data.append(resp)
    return ' '.join(source_data)


class BaseCache:
    key = ''
    summary_key = RedisKey.summary_cache  # 汇总数据
    info_key = RedisKey.cache_info_hash  # 缓存信息
    label = ''

    @staticmethod
    def _json_decode(data: str):
        return json.loads(data)

    @staticmethod
    def _json_encode(data: dict):
        return json.dumps(data, ensure_ascii=False)

    def init_cache(self):
        """
        初始化缓存
        :return:
        """

    @classmethod
    def get(cls, object_id):
        return cache.hget(cls.key, object_id, content_type='json')

    @classmethod
    def set(cls, object_id, data: dict):
        cache.hset(cls.key, object_id, data)

    @classmethod
    def all(cls):
        """
        返回所有数据
        :return:
        """
        return cache.hgetall(cls.key, content_type='json')

    @classmethod
    def update(cls, object_id, object_dict: dict):
        """
        更新[相册/标签/卷]指定ID的缓存
        :param object_id: 缓存ID
        :param object_dict: {'id': 'id', 'name': 'name', 'pv': pv, 'rate': rate, 'count': count }
        :return:
        """
        data_dict = cls.get(object_id)
        if data_dict:
            data_dict.update(object_dict)
        else:
            data_dict = object_dict
        return cls.set(object_id, data_dict)

    @classmethod
    def common_update(cls, album_type, object_id, object_dict):
        """更新缓存"""
        if album_type == 'album':
            return AlbumCache.update(object_id, object_dict)
        elif album_type == 'actress':
            return ActressCache.update(object_id, object_dict)
        elif album_type == 'volume':
            return VolumeCache.update(object_id, object_dict)
        elif album_type == 'tag':
            return TagCache.update(object_id, object_dict)

    @classmethod
    def clear(cls):
        return cache.delete(cls.key)

    @classmethod
    def summary_data(cls):
        """返回 :cache:summary 下的数据"""
        return cache.hgetall(cls.summary_key, content_type='int')

    @classmethod
    def cache_info_set(cls, key: str, value: dict):
        cache.hset(cls.info_key, key, value)

    @property
    def cache_info(self) -> dict:
        return cache.hget(self.info_key, self.label, content_type='json')

    @classmethod
    def pics_count_refresh(cls, album_type, object_id, incr):
        """
        更新缓存中的图片数量
        :param album_type:
        :param object_id:
        :param incr: 图片变化的数量
        :return:
        """
        if album_type == 'album':
            cache_data = AlbumCache.get(object_id)
            AlbumCache.update(object_id, {'count': cache_data.get('count') + incr})
        elif album_type == 'actress':
            cache_data = ActressCache.get(object_id)
            ActressCache.update(object_id, {'count': cache_data.get('count') + incr})
        elif album_type == 'volume':
            cache_data = VolumeCache.get(object_id)
            VolumeCache.update(object_id, {'count': cache_data.get('count') + incr})
        elif album_type == 'tag':
            cache_data = TagCache.get(object_id)
            TagCache.update(object_id, {'count': cache_data.get('count') + incr})

    @classmethod
    def select_options(cls, label='', group_data: dict = None):
        if not label:
            label = cls.label

        if not group_data:
            group_data = {}

        if label == 'album':
            cache_data = site_info.all_albums
        elif label == 'actress':
            cache_data = site_info.all_actresses
        elif label == 'volume':
            cache_data = site_info.all_volumes
        elif label == 'tag':
            cache_data = site_info.all_tags
        else:
            return


        no_group_data = [f'<optgroup label="未分类" class="未分类">']
        for item in cache_data:
            if not item['meta'].get('archived'):
                option = f"<option value='{item['id']}'>{item['name']}&lt;span style='display:none'&gt;{item['pinyin']}&lt;/span&gt;</option>"
                if label == 'volume':
                    group_name = item['actress_id']
                else:
                    group_name = item['meta'].get('option_group')
                if group_name:
                    if group_name in group_data:
                        group_data[group_name].append(option)
                    else:
                        group_data[group_name] = [f'<optgroup label="{group_name}" class="{group_name}">', option]
                else:
                    no_group_data.append(option)
        group_data['未分类'] = no_group_data
        result = []
        for _, options in group_data.items():
            result.extend(options)
            result.append('</optgroup>')
        return ''.join(result)


class AlbumCache(BaseCache):
    key = RedisKey.albums_cache
    label = Album.label

    @classmethod
    def init_cache(cls):
        res = db.session.query(Album, func.count(pic_albums.c.album_id)).join(pic_albums, isouter=True).group_by(
            Album).all()
        album_count = 0
        cache_dict = {}
        for album, count in res:
            album_count += 1
            cache_dict[album.id] = cls._json_encode({
                'id': album.id,
                'name': album.standard_name,
                'count': count,
                'pv': album.pv,
                'pinyin': album.pinyin,
                'meta': album.meta,
            })
        if cache_dict:
            cls.clear()
            cache.hmset(cls.key, cache_dict)
        cls.cache_info_set(cls.label, {
            'count': album_count,
            'refresh_time': int(time.time()),
        })


class TagCache(BaseCache):
    key = RedisKey.tags_cache
    label = Tag.label

    @classmethod
    def init_cache(cls):
        res = db.session.query(Tag, func.count(pic_tags.c.pic_id)).join(pic_tags, isouter=True).group_by(
            Tag).all()
        tag_count = 0
        cache_dict = {}
        for tag, count in res:
            tag_count += 1
            cache_dict[tag.id] = cls._json_encode({
                'id': tag.id,
                'name': tag.standard_name,
                'count': count,
                'pv': tag.pv,
                'pinyin': tag.pinyin,
                'meta': tag.meta,
            })
        if cache_dict:
            cls.clear()
            cache.hmset(cls.key, cache_dict)
        cls.cache_info_set(cls.label, {
            'count': tag_count,
            'refresh_time': int(time.time()),
        })


class VolumeCache(BaseCache):
    key = RedisKey.volumes_cache
    label = Volume.label

    @classmethod
    def init_cache(cls):
        res = db.session.query(Volume, func.count(pic_volumes.c.pic_id)).join(pic_volumes, isouter=True).group_by(
            Volume).all()
        volume_count = 0
        cache_dict = {}
        for volume, count in res:
            volume_count += 1
            cache_dict[volume.id] = cls._json_encode({
                'id': volume.id,
                'name': volume.standard_name,
                'actress_id': volume.actress_id,
                'count': count,
                'pv': volume.pv,
                'meta': volume.meta,
                'pinyin': f'{volume.actress.pinyin},{volume.pinyin}',
            })
        if cache_dict:
            cls.clear()
            cache.hmset(cls.key, cache_dict)
        cls.cache_info_set(cls.label, {
            'count': volume_count,
            'refresh_time': int(time.time()),
        })


class ActressCache(BaseCache):
    key = RedisKey.actresses_cache
    label = Actress.label

    @classmethod
    def init_cache(cls):
        res = db.session.query(Actress, func.count(pic_actresses.c.actress_id)).join(pic_actresses, isouter=True).group_by(
            Actress).all()
        actress_count = 0
        cache_dict = {}
        for actress, count in res:
            actress_count += 1
            cache_dict[actress.id] = cls._json_encode({
                'id': actress.id,
                'name': actress.standard_name,
                'count': count,
                'pv': actress.pv,
                'pinyin': actress.pinyin,
                'meta': actress.meta,
            })
        if cache_dict:
            cls.clear()
            cache.hmset(cls.key, cache_dict)
        cls.cache_info_set(cls.label, {
            'count': actress_count,
            'refresh_time': int(time.time()),
        })


def title_size(num):
    if num <= 20:
        return 'text-size-1'
    elif 20 < num <= 50:
        return 'text-size-2'
    elif 50 < num <= 100:
        return 'text-size-3'
    elif 100 < num <= 300:
        return 'text-size-4'
    elif 300 < num <= 600:
        return 'text-size-5'
    elif 600 < num <= 1000:
        return 'text-size-6'
    elif 1000 < num <= 2000:
        return 'text-size-7'
    elif 2000 < num <= 5000:
        return 'text-size-8'
    elif 5000 < num <= 15000:
        return 'text-size-9'
    elif 15000 < num <= 30000:
        return 'text-size-10'
    else:
        return 'text-size-11'


class SiteInfo:

    def __init__(self):
        self.album_cache = AlbumCache()
        self.tag_cache = TagCache()
        self.actress_cache = ActressCache()
        self.volume_cache = VolumeCache()

    @staticmethod
    def per_page_pics():
        return Setting.get('per_page_pics')

    @property
    def pic_count(self):
        return Pic.query.count()

    @property
    def album_count(self):
        return self.album_cache.cache_info.get('count', 0)

    def cache_refresh_time(self, album_label: str):
        cache_info = getattr(self, f'{album_label}_cache').cache_info
        return cache_info.get('refresh_time', '')

    @property
    def tag_count(self):
        return self.tag_cache.cache_info.get('count', 0)

    @property
    def volume_count(self):
        return self.volume_cache.cache_info.get('count', 0)

    @property
    def actress_count(self):
        return self.actress_cache.cache_info.get('count', 0)

    @property
    def all_albums(self):
        albums = list(self.album_cache.all().values())
        albums.sort(key=lambda x: x.get('count'), reverse=True)
        return albums

    @property
    def all_tags(self):
        tags = list(self.tag_cache.all().values())
        tags.sort(key=lambda x: x.get('count'), reverse=True)
        return tags

    @property
    def all_volumes(self):
        volumes = list(self.volume_cache.all().values())
        volumes.sort(key=lambda x: x.get('name'), reverse=True)
        return volumes

    @property
    def all_actresses(self):
        actresses = list(self.actress_cache.all().values())
        actresses.sort(key=lambda x: x.get('count'), reverse=True)
        return actresses

    @staticmethod
    def count_temp():
        """ 待导入图片数量 """
        return cache.hlen(RedisKey.temp_pics_hash)

    @staticmethod
    def count_recycle():
        """回收站中图片数量"""
        return Pic.query.filter(Pic.delete_time.isnot(None)).count()

    def per_star_count(self):
        """每个评分等级的图片数量汇总"""
        # todo 放入缓存
        all_pics_count = self.pic_count
        data = Pic.query.with_entities(Pic.rate, func.count(Pic.rate)).group_by(Pic.rate).order_by(Pic.rate.desc()).all()
        resp = []
        for star, count in data:
            resp.append([star, count, round(count/all_pics_count*100, 2)])
        return resp

    @staticmethod
    def get_cache(album_type, album_id):
        album_id = str(album_id)
        if album_type == 'album':
            cache_data = AlbumCache.get(album_id)
        elif album_type == 'tag':
            cache_data = TagCache.get(album_id)
        elif album_type == 'volume':
            cache_data = VolumeCache.get(album_id)
        elif album_type == 'actress':
            cache_data = ActressCache.get(album_id)
        else:
            cache_data = dict()

        return cache_data

    @property
    def cache_info(self):
        return BaseCache.cache_info


site_info = SiteInfo()
